package com.hzw.saas.web.admin.job.task;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import javax.annotation.Resource;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.hzw.saas.common.config.util.RedisUtil;
import com.hzw.saas.common.quartz.model.ScheduleJob;
import com.hzw.saas.common.util.AccountValidatorUtil;
import com.hzw.saas.common.util.Json;
import com.hzw.saas.common.util.MapperUtil;
import com.hzw.saas.common.verify.service.IEmailService;
import com.hzw.saas.web.admin.job.model.VpnConfig;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.Session;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.ssh.JschUtil;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * VPN HA机制
 * @Description
 * @Date 2021/6/23 3:41 下午
 * @Created by sonam
 */
@Slf4j
@Component("vpnServiceTask")
public class VpnServiceTask {
    @Resource(name = "executor")
    private Executor executor = Executors.newCachedThreadPool();
    @Resource
    private IEmailService emailService;

    @SneakyThrows
    public void vpnService(ScheduleJob scheduleJob) {
        String params = scheduleJob.getParams();
        VpnConfig vpnConfig = parseParams(params);

        if(vpnConfig == null) {
            throw new IllegalArgumentException("VPN-JOB 参数异常");
        }
        Session session = getSession(vpnConfig);

        if(vpnService(session, vpnConfig.getCheckVpnCommandList(), vpnConfig.getCheckVpnCommandSuccessMsgList())) {
            log.info("VPN 已连接");
        } else {
            // VPN 重拨
            if (!vpnService(session, vpnConfig.getVpnCommandList(), vpnConfig.getVpnCommandSuccessMsgList())) {
                log.info("VPN 重拨失败");
                List<String> notifyEmail = vpnConfig.getNotifyEmail();
                // 发邮件？
                if(vpnConfig.isSendEmail() && CollectionUtils.isNotEmpty(notifyEmail)) {
                    notifyEmail.forEach(email -> {
                        log.info("send to email >- {} -<", email);
                        if(AccountValidatorUtil.isEmail(email)) {
                            String key = "notify:" + email;
                            Object value = RedisUtil.get(key);
                            if(value == null) {
                                // 一小时之内通知一次
                                RedisUtil.set(key, 0, vpnConfig.getNotifyInterval() == null ? 3600 : vpnConfig.getNotifyInterval() );
                                emailService.sendVerifyCode(email, "VPN重拨预警", Json.toJsonString(vpnConfig));
                            } else {
                                log.info("Has been sent email >- {} -< [skip]", email);
                            }
                        }
                    });
                } else {
                    log.info("Don't send emails");
                }
            } else {
                log.info("VPN 重拨成功");
            }
        }
    }

    @SneakyThrows
    private boolean vpnService(Session session, List<String> commandList, List<String> commandSuccessList) {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = (ThreadPoolTaskExecutor) executor;
        Future<Boolean> redialThread = threadPoolTaskExecutor.submit(new CommandExecutorThread(session, commandList, commandSuccessList));
        try {
           return redialThread.get(10, TimeUnit.SECONDS);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获取Session，缓存
     * @param vpnConfig
     * @return
     */
    @SneakyThrows
    private Session getSession(VpnConfig vpnConfig) {
        String sshHost = vpnConfig.getSshHost();
        int sshPort = vpnConfig.getSshPort();
        String sshUser = vpnConfig.getSshUser();
        String privateKeyPath = vpnConfig.getPrivateKeyPath();

        String key = StrUtil.format("{}@{}:{}", sshUser, sshHost, sshPort);
        Session session = JschUtil.getSession(sshHost, sshPort, sshUser, privateKeyPath, null);
        if(session == null || !session.isConnected()) { // 当session失效
            JschUtil.close(key);
            // 睡1s，防止频繁重复连接
            TimeUnit.SECONDS.sleep(1);
            log.info("连接失效: {}，正在尝试重新连接", key);
            return getSession(vpnConfig);
        }
        return session;
    }

    /**
     * 开启SSH 通道，执行某个指令，当返回指定结果时返回true，超时或者异常返回false
     */
    @AllArgsConstructor
    private class CommandExecutorThread implements Callable<Boolean> {

        private Session session;
        private List<String> commands;
        private List<String> stopMsgs;

        @Override
        public Boolean call() throws Exception {
            Channel channel = session.openChannel("shell");
            channel.connect();
            try {
                InputStream inputStream = channel.getInputStream();
                commands.forEach(command -> {
                    try {
                        transToSSH(channel, command + "\r\n");
                    } catch (IOException e) { e.printStackTrace(); }
                });
                byte[] buffer = new byte[1024];
                int i;
                //如果没有数据来，线程会一直阻塞在这个地方等待数据。
                while ((i = inputStream.read(buffer)) != -1) {
                    byte[] payload = Arrays.copyOfRange(buffer, 0, i);
                    String msg = new String(payload, "UTF-8");
                    log.info(msg);
                    for (String stopMsg : stopMsgs) {
                        if(msg.toLowerCase().contains(stopMsg.toLowerCase()))
                            return true;
                    }
                    // 否则超时，线程断开
                }
                return false;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            } finally {
                if(channel != null) channel.disconnect();
            }
        }

        private void transToSSH(Channel channel, String command) throws IOException {
            if (channel != null) {
                OutputStream outputStream = channel.getOutputStream();
                outputStream.write(command.getBytes());
                outputStream.flush();
            }
        }
    }

    private VpnConfig parseParams(String params) {
        try {
            Map<String, String> map = Json.getObjectMapper().readValue(params, Map.class);
            VpnConfig vpnConfig = MapperUtil.nf().map(map, VpnConfig.class);
            return vpnConfig;
        } catch (Exception e) {
            log.info("VPN参数解析错误", e);
        }
        return null;
    }

}
